package com.coocaa.app.support.net;

import com.coocaa.app.support.thread.CCThread;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.fastjson.FastJsonConverterFactory;

import static com.coocaa.app.support.net.HttpServiceManager.HttpServiceManagerConfig.CONNECT_TIMEOUT;
import static com.coocaa.app.support.net.HttpServiceManager.HttpServiceManagerConfig.READ_TIMEOUT;
import static com.coocaa.app.support.net.HttpServiceManager.HttpServiceManagerConfig.WRITE_TIMEOUT;

public abstract class HttpServiceManager<T> {
    public interface HttpServiceMonitor {
        void submit(String method, Map<String, String> headers, int httpCode, int errCode, String errMsg, long duration);
    }

    public static class HttpServiceManagerConfig {
        static final int CONNECT_TIMEOUT = 30;
        static final int READ_TIMEOUT = 30;
        static final int WRITE_TIMEOUT = 30;
        public int connectTimeout = CONNECT_TIMEOUT;
        public int readTimeout = READ_TIMEOUT;
        public int writeTimeout = WRITE_TIMEOUT;

        public Map<String, String> loadHeaders() {
            return null;
        }

        public boolean isNetConnected() {
            return true;
        }

        public HttpServiceMonitor getHttpServiceMonitor() {
            return null;
        }
    }

    private static final Executor executor = Executors.newFixedThreadPool(4, CCThread.newThreadFactory("http-service"));

    private HttpServiceManagerConfig config;
    private OkHttpClient client;
    private T http, https;

    public HttpServiceManager(String domain) {
        this(domain, null);
    }

    public HttpServiceManager(String domain, HttpServiceManagerConfig config) {
        this(domain, null, 0, config);
    }

    public HttpServiceManager(String domain, File cacheDir, long cacheSize, HttpServiceManagerConfig config) {
        this.config = config;
        client = buildClient(cacheDir, cacheSize, config);
        http = new Retrofit.Builder()
                .client(client)
                .baseUrl("http://" + domain)
                .addConverterFactory(FastJsonConverterFactory.create())
                .build()
                .create(getServiceClazz());
        https = new Retrofit.Builder()
                .client(client)
                .baseUrl("https://" + domain)
                .addConverterFactory(FastJsonConverterFactory.create())
                .build()
                .create(getServiceClazz());
    }

    private OkHttpClient buildClient(final File cacheDir, long cacheSize, final HttpServiceManagerConfig config) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request.Builder requestBuilder = chain.request().newBuilder();
                if (config != null) {
                    Map<String, String> headers = config.loadHeaders();
                    if (headers != null) {
                        Set<Map.Entry<String, String>> entries = headers.entrySet();
                        for (Map.Entry<String, String> entry : entries)
                            requestBuilder.addHeader(entry.getKey(), entry.getValue());
                    }
                }
                if (cacheDir != null && config != null) {
                    if (!config.isNetConnected()) {
                        requestBuilder.cacheControl(CacheControl.FORCE_CACHE);
                    }
                }
                return chain.proceed(requestBuilder.build());
            }
        });
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(loggingInterceptor);
        builder.connectTimeout(config != null ? config.connectTimeout : CONNECT_TIMEOUT, TimeUnit.SECONDS);
        builder.readTimeout(config != null ? config.readTimeout : READ_TIMEOUT, TimeUnit.SECONDS);
        builder.writeTimeout(config != null ? config.writeTimeout : WRITE_TIMEOUT, TimeUnit.SECONDS);
        if (cacheDir != null) {
            builder.cache(new Cache(cacheDir, cacheSize));
        }
        return builder.build();
    }

    protected final T getHttp() {
        return http;
    }

    protected final T getHttps() {
        return https;
    }

    protected abstract Class<T> getServiceClazz();


    public class SeqExecuteHandler<T> {
        private boolean bFlag = false;

        public void call(Call<T> call, final Callback<Result<T>> callback) {
            synchronized (SeqExecuteHandler.this) {
                if (bFlag)
                    return;
                bFlag = true;
            }
            execute(call, new Callback<Result<T>>() {
                @Override
                public Ret onResult(Result<T> result) {
                    Ret ret = callback.onResult(result);
                    synchronized (SeqExecuteHandler.this) {
                        bFlag = false;
                    }
                    return ret;
                }
            });
        }
    }


    public final <T> void execute(final Call<T> call, final Callback<Result<T>> callback) {
        executor.execute(new Runnable() {
            private void monitor(String method, Map<String, String> headers, int httpCode, int errCode, String errMsg, long duration) {
                if (config != null) {
                    HttpServiceMonitor monitor = config.getHttpServiceMonitor();
                    if (monitor != null) {
                        monitor.submit(method, headers, httpCode, errCode, errMsg, duration);
                    }
                }
            }

            private Map<String, String> headers2map(Headers headers) {
                Map<String, String> _headers = new HashMap<>();
                Set<String> keys = headers.names();
                for (String key : keys)
                    _headers.put(key, headers.get(key));
                return _headers;
            }

            @Override
            public void run() {
                long st = System.nanoTime();
                Response _response = null;
                Result<T> result = new Result();
                try {
                    retrofit2.Response<T> response = call.execute();
                    _response = response.raw();
                    result.duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - st);
                    result.code = response.code();
                    result.data = response.body();
                } catch (Exception e) {
                    e.printStackTrace();
                    result.exception = e;
                }
                String method = null;
                Map<String, String> headers = null;
                if (_response != null) {
                    method = _response.request().url().toString();
                    headers = headers2map(_response.request().headers());
                }
                if (result.exception != null) {
                    if (method != null)
                        monitor(method, headers, result.code, -1, result.exception.getMessage(), result.duration);
                }
                if (callback != null) {
                    Callback.Ret ret = callback.onResult(result);
                    if (ret != null) {
                        if (method != null)
                            monitor(method, headers, result.code, ret.code, ret.msg, result.duration);
                    }
                }
            }
        });
    }
}
